#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Version        : 1.2
# @Update Time    : 2025/8/26 10:00
# @File           : base_enhanced.py
# @IDE            : PyCharm
# @Desc           : 增强版文件上传抽象基类，定义了文件上传的通用接口和工具方法

import os
import random
import string
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import List, Optional, Dict, Any, Union

from fastapi import UploadFile

from applications.config import settings
from applications.core.exceptions import BaseAppException
from .file_utils import FileUtils


class UploadResult:
    """文件上传结果类"""

    def __init__(
        self,
        success: bool = True,
        file_path: Optional[str] = None,
        url: Optional[str] = None,
        file_info: Optional[Dict] = None,
        thumbnail_path: Optional[str] = None,
        message: Optional[str] = None,
        error: Optional[str] = None,
    ):
        self.success = success
        self.file_path = file_path
        self.url = url
        self.file_info = file_info or {}
        self.thumbnail_path = thumbnail_path
        self.message = message
        self.error = error

    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        return {
            "success": self.success,
            "file_path": self.file_path,
            "url": self.url,
            "file_info": self.file_info,
            "thumbnail_path": self.thumbnail_path,
            "message": self.message,
            "error": self.error,
        }


class FileType(Enum):
    """文件类型枚举"""

    IMAGE = "image"
    VIDEO = "video"
    AUDIO = "audio"
    EXCEL = "excel"
    PDF = "pdf"
    WORD = "word"
    OTHER = "other"


class AbstractUpload(ABC):
    """文件上传抽象基类，定义了文件上传的通用接口和工具方法

    Attributes:
        IMAGE_ACCEPT: 支持的图片类型
        VIDEO_ACCEPT: 支持的视频类型
        AUDIO_ACCEPT: 支持的音频类型
        EXCEL_ACCEPT: 支持的Excel类型
        PDF_ACCEPT: 支持的PDF类型
        WORD_ACCEPT: 支持的Word类型
        OTHER_ACCEPT: 支持的其他类型
        FILE_ACCEPT: 所有支持的文件类型
        ALL_ACCEPT: 所有支持的类型
        UPLOAD_PATH: 上传文件基础路径
    """

    # 文件类型定义
    IMAGE_ACCEPT: List[str] = ["image/png", "image/jpeg", "image/gif", "image/x-icon", "image/webp"]
    VIDEO_ACCEPT: List[str] = ["video/mp4", "video/mpeg", "video/webm", "video/avi"]
    AUDIO_ACCEPT: List[str] = [
        "audio/wav",
        "audio/mp3",
        "audio/m4a",
        "audio/wma",
        "audio/ogg",
        "audio/mpeg",
        "audio/x-wav",
        "audio/flac",
    ]
    EXCEL_ACCEPT: List[str] = [
        "application/vnd.ms-excel",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    ]
    PDF_ACCEPT: List[str] = ["application/pdf"]
    WORD_ACCEPT: List[str] = [
        "application/msword",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    ]
    OTHER_ACCEPT: List[str] = ["application/zip", "application/x-rar", "text/plain"]

    # 合并所有支持的类型
    FILE_ACCEPT: List[str] = [*EXCEL_ACCEPT, *PDF_ACCEPT, *WORD_ACCEPT, *OTHER_ACCEPT]
    ALL_ACCEPT: List[str] = [*IMAGE_ACCEPT, *VIDEO_ACCEPT, *AUDIO_ACCEPT, *FILE_ACCEPT]

    # 上传文件基础路径 - 直接使用配置文件中的完整物理路径
    UPLOAD_PATH: str = settings.UPLOAD_PATH

    # 文件上传配置
    DEFAULT_MAX_SIZE: int = settings.UPLOAD_MAX_SIZE
    LARGE_FILE_MAX_SIZE: int = settings.UPLOAD_LARGE_FILE_MAX_SIZE
    CHUNK_SIZE: int = settings.UPLOAD_CHUNK_SIZE * 1024 * 1024  # 转换为字节
    ALLOWED_EXTENSIONS: List[str] = settings.UPLOAD_ALLOWED_EXTENSIONS
    THUMBNAIL_SIZE: tuple = settings.UPLOAD_IMAGE_THUMBNAIL_SIZE
    IMAGE_QUALITY: int = settings.UPLOAD_IMAGE_QUALITY

    # 抽象方法 - 基础上传方法
    @abstractmethod
    async def upload_video(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传视频文件"""
        pass

    @abstractmethod
    async def upload_image(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传图片文件"""
        pass

    @abstractmethod
    async def upload_audio(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传音频文件"""
        pass

    @abstractmethod
    async def upload_file(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传普通文件"""
        pass

    @abstractmethod
    async def upload_large_file(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 50
    ) -> str:
        """上传大文件"""
        pass

    @abstractmethod
    async def delete_file(self, file_path: str) -> bool:
        """删除文件"""
        pass

    @abstractmethod
    async def get_file_info(self, file_path: str) -> dict:
        """获取文件信息"""
        pass

    # 抽象方法 - 增强功能
    @abstractmethod
    async def upload_with_result(
        self,
        path: str,
        file: UploadFile,
        accept: Optional[List[str]] = None,
        max_size: int = 5,
        create_thumbnail: bool = False,
    ) -> UploadResult:
        """上传文件并返回详细结果"""
        pass

    @abstractmethod
    async def batch_upload(
        self, path: str, files: List[UploadFile], accept: Optional[List[str]] = None, max_size: int = 5
    ) -> List[UploadResult]:
        """批量上传文件"""
        pass

    @abstractmethod
    async def get_file_url(self, file_path: str) -> str:
        """获取文件访问URL"""
        pass

    @abstractmethod
    async def file_exists(self, file_path: str) -> bool:
        """检查文件是否存在"""
        pass

    # 类方法 - 工具方法
    @classmethod
    def get_storage_strategy(cls) -> str:
        """获取存储策略类型"""
        return "unknown"

    @classmethod
    def get_max_file_size(cls, file_type: str = "default") -> int:
        """根据文件类型获取最大文件大小"""
        size_config = {"image": 10, "video": 100, "audio": 50, "document": 20, "default": cls.DEFAULT_MAX_SIZE}
        return size_config.get(file_type, cls.DEFAULT_MAX_SIZE)

    @classmethod
    async def validate_file_advanced(
        cls,
        file: UploadFile,
        max_size: Optional[int] = None,
        accept: Optional[List[str]] = None,
        check_content: bool = True,
    ) -> Dict[str, Any]:
        """高级文件验证"""
        if not accept:
            accept = cls.ALL_ACCEPT

        if max_size is None:
            file_type = FileUtils.get_file_type_by_extension(file.filename or "")
            max_size = cls.get_max_file_size(file_type)

        # 使用FileUtils进行验证
        return await FileUtils.validate_upload_file(
            file=file, max_size=max_size, allowed_extensions=cls.ALLOWED_EXTENSIONS, allowed_mime_types=accept
        )

    @classmethod
    def generate_safe_filename(cls, filename: str) -> str:
        """生成安全的文件名"""
        # 首先清理文件名
        clean_name = FileUtils.clean_filename(filename)

        # 获取文件扩展名
        ext = Path(clean_name).suffix
        name_without_ext = Path(clean_name).stem

        # 生成随机字符串
        random_str = "".join(random.choices(string.ascii_letters + string.digits, k=8))
        # 生成时间戳
        timestamp = datetime.now().strftime("%Y%m%d%H%M%S")

        # 组合文件名
        if len(name_without_ext) > 50:  # 限制原始文件名长度
            name_without_ext = name_without_ext[:50]

        return f"{timestamp}_{random_str}_{name_without_ext}{ext}"

    @classmethod
    def generate_file_name(cls, filename: str) -> str:
        """生成安全的文件名（保持向后兼容）"""
        return cls.generate_safe_filename(filename)

    @classmethod
    def generate_relative_path(
        cls,
        path: str,
        filename: Optional[str] = None,
        suffix: Optional[str] = None,
        is_today: bool = True,
        create_subdirs: bool = True,
    ) -> str:
        """生成相对路径（相对于静态文件目录的路径）"""
        # 标准化路径
        path = Path(path).as_posix()
        if path.startswith("/"):
            path = path[1:]

        # 验证路径安全性（使用静态目录名称进行验证）
        from applications.config import settings

        static_directory = settings.UPLOAD_PATH
        if not FileUtils.is_safe_path(path, static_directory):
            raise BaseAppException(msg="不安全的文件路径")

        # 验证参数
        if not filename and not suffix:
            raise BaseAppException(msg="文件名或文件后缀不能同时为空")

        # 处理后缀
        if not suffix:
            if not filename or "." not in filename:
                raise BaseAppException(msg="文件名必须带有后缀")
            suffix = Path(filename).suffix
            filename = Path(filename).stem
        else:
            if not suffix.startswith("."):
                suffix = "." + suffix

        # 生成安全的文件名
        if not filename:
            safe_filename = cls.generate_safe_filename(f"temp{suffix}")
        else:
            safe_filename = cls.generate_safe_filename(filename + suffix)

        # 构建相对路径（不包含物理路径前缀）
        path_parts = [path]

        # 添加日期目录
        if is_today:
            today = datetime.today().strftime("%Y%m%d")
            path_parts.append(today)

        # 根据文件类型创建子目录
        if create_subdirs:
            file_type = FileUtils.get_file_type_by_extension(safe_filename)
            path_parts.append(file_type)

        path_parts.append(safe_filename)

        # 返回相对路径（相对于静态文件目录）
        relative_path = "/".join(path_parts)
        return relative_path

    @classmethod
    def get_url_from_relative_path(cls, relative_path: str) -> str:
        """根据相对路径生成完整的URL路径

        Args:
            relative_path: 相对于静态文件目录的路径

        Returns:
            str: 完整的URL路径
        """
        from applications.config import settings

        static_url_path = settings.STATIC_URL_PATH
        return f"{static_url_path.rstrip('/')}/{relative_path}"

    @classmethod
    def generate_url_path(
        cls,
        path: str,
        filename: Optional[str] = None,
        suffix: Optional[str] = None,
        is_today: bool = True,
        create_subdirs: bool = True,
    ) -> str:
        """生成完整的URL路径（包含FastAPI拦截路径前缀），用于存储到数据库

        Args:
            path: 保存路径
            filename: 文件名
            suffix: 文件后缀
            is_today: 是否添加日期目录
            create_subdirs: 是否创建子目录

        Returns:
            str: 完整的URL路径（如 /static/photos/20250826/image/test.jpg）
        """
        # 先生成相对路径
        relative_path = cls.generate_relative_path(path, filename, suffix, is_today, create_subdirs)
        # 基于相对路径生成URL
        return cls.get_url_from_relative_path(relative_path)

    @classmethod
    def get_physical_path(cls, relative_path: str) -> str:
        """根据相对路径获取完整的物理存储路径

        Args:
            relative_path: 相对于静态文件目录的路径

        Returns:
            str: 完整的物理存储路径
        """
        # 清理路径，移除开头的斜杠
        clean_path = relative_path.lstrip("/")
        # 组合完整的物理路径
        return os.path.join(cls.UPLOAD_PATH, clean_path)

    @classmethod
    async def validate_file(cls, file: UploadFile, max_size: int = 5, accept: Optional[List[str]] = None) -> None:
        """验证文件（保持向后兼容）"""
        # 调用高级验证方法，但只抛出异常不返回结果
        await cls.validate_file_advanced(file, max_size, accept)

    @classmethod
    async def create_thumbnail_if_image(cls, file_path: str, thumbnail_path: Optional[str] = None) -> Optional[str]:
        """如果是图片则创建缩略图"""
        file_type = FileUtils.get_file_type_by_extension(file_path)
        if file_type != "image":
            return None

        if thumbnail_path is None:
            path_obj = Path(file_path)
            thumbnail_path = str(path_obj.parent / "thumbnails" / f"thumb_{path_obj.name}")

        try:
            success = FileUtils.create_thumbnail(
                image_path=file_path, thumbnail_path=thumbnail_path, size=cls.THUMBNAIL_SIZE, quality=cls.IMAGE_QUALITY
            )
            return thumbnail_path if success else None
        except Exception:
            return None

    @classmethod
    def get_file_type(cls, file: UploadFile) -> FileType:
        """获取文件类型（保持向后兼容）"""
        content_type = file.content_type
        if content_type in cls.IMAGE_ACCEPT:
            return FileType.IMAGE
        elif content_type in cls.VIDEO_ACCEPT:
            return FileType.VIDEO
        elif content_type in cls.AUDIO_ACCEPT:
            return FileType.AUDIO
        elif content_type in cls.EXCEL_ACCEPT:
            return FileType.EXCEL
        elif content_type in cls.PDF_ACCEPT:
            return FileType.PDF
        elif content_type in cls.WORD_ACCEPT:
            return FileType.WORD
        else:
            return FileType.OTHER

    @classmethod
    async def save_with_advanced_options(
        cls,
        path: str,
        file: UploadFile,
        accept: Optional[List[str]] = None,
        max_size: int = 5,
        create_thumbnail: bool = False,
        compress_image: bool = False,
    ) -> UploadResult:
        """高级文件保存方法"""
        try:
            # 验证文件并获取详细信息
            file_info = await cls.validate_file_advanced(file, max_size, accept)

            # 生成相对路径（相对于静态文件目录）
            relative_path = cls.generate_relative_path(path, file.filename)

            # 基于相对路径生成完整的URL路径（用于存储到数据库）
            url_path = cls.get_url_from_relative_path(relative_path)

            # 获取完整的物理存储路径
            physical_path = cls.get_physical_path(relative_path)

            # 确保目录存在
            save_path = Path(physical_path)
            save_path.parent.mkdir(parents=True, exist_ok=True)

            # 保存文件内容
            content = await file.read()
            with open(save_path, "wb") as f:
                f.write(content)

            # 获取文件详细信息
            detailed_info = FileUtils.get_file_info(str(save_path))

            result = UploadResult(
                success=True,
                file_path=url_path,  # 返回完整URL路径给前端（用于存储到数据库）
                url=url_path,  # 同样使用完整URL路径
                file_info=detailed_info,
                message="文件上传成功",
            )

            # 如果是图片，进行图片处理
            if detailed_info.get("file_type") == "image":
                # 压缩图片
                if compress_image:
                    try:
                        FileUtils.compress_image(
                            str(save_path),
                            quality=cls.IMAGE_QUALITY,
                            max_width=1920,  # 最大宽度
                            max_height=1080,  # 最大高度
                        )
                    except Exception:
                        pass  # 压缩失败不影响上传

                # 创建缩略图
                if create_thumbnail:
                    thumbnail_path = await cls.create_thumbnail_if_image(str(save_path))
                    if thumbnail_path:
                        # 转换为相对路径
                        thumbnail_relative = os.path.relpath(thumbnail_path, cls.UPLOAD_PATH).replace("\\", "/")
                        # 生成完整的缩略图URL路径
                        from applications.config import settings

                        static_url_path = settings.STATIC_URL_PATH
                        thumbnail_url = f"{static_url_path.rstrip('/')}/{thumbnail_relative}"
                        result.thumbnail_path = thumbnail_url

            return result

        except BaseAppException:
            raise
        except Exception as e:
            raise BaseAppException(msg=f"保存文件失败: {str(e)}")
        finally:
            await file.close()

    @classmethod
    async def save(cls, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5) -> str:
        """保存文件的通用方法（保持向后兼容）"""
        result = await cls.save_with_advanced_options(path, file, accept, max_size)
        if not result.file_path:
            raise BaseAppException(msg="文件保存失败，未获取到文件路径")
        return result.file_path
